using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Java.Lang;
using Java.Sql;

namespace MapDrawer
{
    public class Parser
    {
        enum Types { Building, Road, Unknow };
        private readonly XmlDocument document;

        /// <summary>
        /// The Minimum latitude. Retrieved from the bounds tag of the XML map descriptor.
        /// </summary>
        public double MinLat
        {
            get
            {
                if (!_minLatIsSet)
                {
                    _minLatIsSet = true;
                    _minLat = Double.ParseDouble(document.GetElementsByTagName("bounds")[0].Attributes["minlat"].Value.Replace(',','.'));
                }
                return _minLat;
            }
        }

        private bool _minLatIsSet = false;
        private double _minLat;
        /// <summary>
        /// The Maximum latitude. Retrieved from the bounds tag of the XML map descriptor.
        /// </summary>
        public double MaxLat
        {
            get
            {
                if (_maxLatIsSet)
                    return _maxLat;
                _maxLatIsSet = true; 
                _maxLat = Double.ParseDouble(document.GetElementsByTagName("bounds")[0].Attributes["maxlat"].Value.Replace(',','.'));
                return _maxLat;
            }
        }
        private bool _maxLatIsSet = false;
        private double _maxLat;
        /// <summary>
        /// The Minimum longitude. Retrieved from the bounds tag of the XML map descriptor.
        /// </summary>
        public double MinLon
        {
            get
            {
                if (_minLonIsSet)
                    return _minLon;
                _minLonIsSet = true;
                _minLon = Double.ParseDouble(document.GetElementsByTagName("bounds")[0].Attributes["minlon"].Value.Replace(',', '.'));
                return _minLon;
            }
        }
        private bool _minLonIsSet = false;
        private double _minLon;
        /// <summary>
        /// The Maximum longitude. Retrieved from the bounds tag of the XML map descriptor.
        /// </summary>
        public double MaxLon
        {
            get
            {
                if (_maxLonIsSet)
                    return _maxLon;
                _maxLonIsSet = true;
                _maxLon = Double.ParseDouble(document.GetElementsByTagName("bounds")[0].Attributes["maxlon"].Value.Replace(',', '.'));
                return _maxLon;
            }
        }

        private bool _maxLonIsSet = false;
        private double _maxLon;
        /// <summary>
        /// Inits a new parser. With the filename given.
        /// </summary>
        /// <param name="filename">A string representing the path to the XML file to load.</param>
        public Parser(string filename)
        {
            document = new XmlDocument();
            document.Load(filename);
        }

        /// <summary>
        /// Inits a new parser. With the given stream.
        /// </summary>
        /// <param name="fs">A stream to an already opened XML file.</param>
        public Parser(Stream fs)
        {
            document = new XmlDocument();
            document.Load(fs);
        }

        /// <summary>
        /// Loads the nodes of the XML file.
        /// </summary>
        /// <returns>A list of the nodes contained in the XML file.</returns>
        public List<Node> LoadNodes()
        {
            var nodesList = document.GetElementsByTagName("node");
            return (from XmlNode node in nodesList let id = Long.ParseLong(node.Attributes["node"].Value) let lat = Float.ParseFloat(node.Attributes["lat"].Value.Replace(',', '.')) let lon = Float.ParseFloat(node.Attributes["lon"].Value.Replace(',', '.')) select new Node(id, lat, lon)).ToList();
        }

        /// <summary>
        /// Loads the ways of the XML file. Ways are either the buildings and the roads.
        /// </summary>
        /// <param name="nodes">A list of the nodes already parsed from the XML.</param>
        /// <param name="buildings">Out parameter which will contain the coordinates of the buildings.</param>
        /// <param name="roads">Out parameter which will contain the coordinates of the roads.</param>
        public void LoadWays(List<Node> nodes, out List<Building> buildings, out List<Road> roads)
        {
            buildings = new List<Building>();
            roads = new List<Road>();
            var ways = document.GetElementsByTagName("way");
            foreach (XmlElement way in ways)
            {
                var tempNodesList = new List<Node>();
                var children = way.ChildNodes;
                var type = Types.Unknow;
                foreach (XmlElement child in children)
                {
                    if (child.Name == "nd")
                    {
                        long id = Long.ParseLong(child.Attributes["ref"].Value);
                        for (int i = 0; i < nodes.Count; i++)
                        {
                            if (nodes[i].Id == id)
                            {
                                tempNodesList.Add(nodes[i]);
                                break;
                            }
                        }
                    }else if (child.Name == "tag")
                    {
                        if (child.Attributes["k"].Value == "highway")
                        {
                            type = Types.Road;
                        }else if (child.Attributes["k"].Value == "building")
                        {
                            type = Types.Building;
                        }
                    }
                    //switch (child.Name)
                    //{
                    //    case "nd":
                    //        tempNodesList.Add(nodes.First(node => node.Id == Long.ParseLong(child.Attributes["ref"].Value)));
                    //        break;
                    //    case "tag":
                    //        switch (child.Attributes["k"].Value)
                    //        {
                    //            case "highway":
                    //                type = Types.Road;
                    //                break;
                    //            case "building":
                    //                type = Types.Building;
                    //                break;
                    //        }
                    //        break;
                    //}
                }
                if (/*type == Types.Unknow ||*/ type == Types.Road)
                {
                    roads.Add(new Road(){Nodes = tempNodesList});
                }else if (type == Types.Building)
                {
                    buildings.Add(new Building(){Nodes = tempNodesList});
                }
            }
        }
    }
}